<?php
/**
 * this is base orm class file
 *
 * this class is for create orm, u can extend this class
 * 
 * @date 2012.6.19
 * @author qixiaona@gmail.com
 * @dependency exceptionhander
 * @access public
 *
 */
require_once(DIR_LIBRARY."exceptionhandler.php");
class ORM 
{
    //for extends from orm class
    protected $dsn;
    protected $tablename;
    protected $rebuildTableName = false;
    protected $validFields = array();

    protected $db;
    protected $handler;

	protected $row;//row from db
    public    $loaded = false;
    protected $props = array();//prop of ORM class
    protected $fields = array();
    protected $classAttr = array();

    protected $where = array();
    protected $what  = NULL;
    protected $order = NULL;
    protected $limit = NULL;
    protected $sqltype = "select";
    protected static $sqltypes = array("select", "update", "delete", "insert");
	
	const MAX_PAGE_SIZE     = 50;
	const DEFAULT_PAGE_SIZE = 10;

    public function __construct($id = NULL)
    {
        $this->props = array_keys(get_class_vars(__CLASS__));//get orm class prop
        $this->handler = new ExceptionHandler();

        if ($id)
        {
            $this->byId($id);
            $this->load();
        }
    }

    public function byId($id)
    {
        $this->appendWhere("`id` = ".$id);
        //$this->setId($id);
    }

    public static function factory($id = NULL)
    {
        return new self($id);
    }

    public function setSQLType($type)
    {
        if (!in_array($type, self::$sqltypes))
        {
            throw new Exception("type is invalid");
        }

        $this->sqltype = $type;
    }

    public function execute()
    {
        return $this->query($this->buildSQL());
    }

    public function load()
    {
        $row = array();
        $this->setSQLType("select");
        $rs = $this->execute();
        
        if (!$rs->isSuccess())
        {
            throw new Exception("query fail");
        }
        
        $row = $rs->fetchRow();
		$this->setRow($row);

        $this->endLoad($row);

        return $this->getLoaded();
    }

	public function getRow()
	{
		return $this->row;
	}

	public function setRow($row)
	{
		$this->row = $row;
	}

    public function endLoad($row)
    {
        if ($row)
        {
            $this->setFields($row);
            $this->loaded = true;
        }
        else
        {
            $this->loaded = false;
        }

        return true;
    }

    public function getTablename()
    {
        if ($this->rebuildTableName)
        {
            return $this->rebuildTableName();
        }
        else
        {
            return $this->tablename;
        }
    }

    public function buildSQL()
    {
		$tablename = $this->getTablename();
		if (!$tablename)
		{
			throw new Exception("no table name");
		}

        switch ($this->getSQLType())
        {
            case "select" :
            {
                $where = $this->buildWhereClause();
                $this->checkWhereClause($where);

                $sql = "SELECT";

                if ($what = &$this->getWhat())
                {
                    $sql .= " ".trim($what);
                }
                else
                {
                    $sql .= " *";
                }

                $sql .= " FROM `".$tablename.'` WHERE '.$where;

                if ($order = &$this->getOrder())
                {
                    $sql .= " ORDER BY ".trim($order);
                }

                if ($limit = &$this->getLimit())
                {
                    $sql .= " LIMIT ".trim($limit).";";
                }
                break;
            }
            case "insert" :
            {
                $slice = $this->formatCreateField();
                $sql = 'INSERT INTO `'.$tablename.'`('.$slice[0].') VALUES('.$slice[1].');';         
                break;
            }
            case "update" ;
            {
                $where = $this->buildWhereClause();
                $this->checkWhereClause($where);
                $field = $this->formatUpdateField();

                if (!$field) 
                {
                    return false;
                }

                $sql = "UPDATE `".$tablename."` SET ".$field.' WHERE '.$where.';';
                break;
            }
            case "delete" :
            {
                $where = $this->buildWhereClause();
                $this->checkWhereClause($where);
                $sql = "DELETE FROM `".$tablename.'` WHERE '.$where.';'; 
                break;
            }
        }

        return $sql;
    }

    protected function getLoaded()
    {
        return $this->loaded;
    }

    protected function setLoaded($bool)
    {
        $this->loaded = $bool;
    }

    public function isLoad()
    {
        return $this->getLoaded();
    }

    public function getListBySequence($sequence = 1, $page_size = self::DEFAULT_PAGE_SIZE, $condition = array(), $pk = "id")
    {
		foreach ($condition as $k => $v)
		{
			$this->appendWhere($k.'="'.$v.'"');
		}

		if (!$this->getWhere())
		{
			$count = $this->getMax("sequence");
		}
		else
		{
			$this->setWhat("count(".$pk.") as count");
			$rs = $this->execute();
			
			if (!$rs->isSuccess())
			{
				throw new NICED_Exception("db query fail");
			}

			$row = $rs->fetchRow();
			$count    = $row["count"];
		}

        $sequence = (int)$sequence;

        $page_size = ($page_size > self::MAX_PAGE_SIZE || $page_size < 1) ? self::DEFAULT_PAGE_SIZE : $page_size;
        $max_page = (int)ceil($count / $page_size);
        
        if ($sequence > $count || $sequence < 1)
        {
            $sequence = 1;
        }

        $s = 'sequence between "'.$sequence.'" AND "'.($sequence + $page_size - 1).'"';
		$this->appendWhere($s);

        $this->setWhat("*");

        if (!$this->getOrder())
        {
            $this->setOrder($pk." DESC");
        }

        $rs = $this->execute();

        if (!$rs->isSuccess())
        {
            throw new NICED_Exception("db query fail");
        }

        $rows = array();
        
        while ($row = $rs->fetchRow())
        {
            $rows[$row[$pk]] = $row;
        }

		return array("list" => $rows, "sequence" => $sequence, "max_page" => $max_page, "page_size" => $page_size, "count" => $count);
    }

    public function getList(&$page = 1, &$page_size = self::DEFAULT_PAGE_SIZE, &$count = 0, &$max_page = 0, $params = array(), $pk = "id")
    {
        if (isset($params["name"]) && $params["name"])
        {
            $this->setName($params["name"]);
        }

        if (!$this->buildWhereClause())
        {
            $this->appendWhere(1);
        }

        
        $this->setWhat("count(".$pk.") as count");
        $rs = $this->execute();
        
        if (!$rs->isSuccess())
        {
            throw new NICED_Exception("db query fail");
        }

        $row = $rs->fetchRow();
        $count    = $row["count"];
        $page = (int)$page;
        $page_size = ($page_size > 50 || $page_size < 1) ? 10 : $page_size;
        $max_page = (int)ceil($count / $page_size);
        
        if ($page > $max_page || $page < 1)
        {
            $page = 1;
        }

        $start = ceil(($page - 1) * $page_size);

        $this->setWhat("*");
        $this->setOrder($pk." desc");
        $this->setLimit($start.",".$page_size);

        $rs = $this->execute();

        if (!$rs->isSuccess())
        {
            throw new NICED_Exception("db query fail");
        }

        $rows = array();
        
        while ($row = $rs->fetchRow())
        {
            $rows[$row[$pk]] = $row;
        }

        return $rows;
    }

    public function getMax($field = NULL)
    {
        if (NULL === $field)
        {
            $field = "id";
        }

        $this->setWhere("1");
        $this->setWhat('max('.$field.') as max_id');
        $rs = $this->execute();

        if (!$rs->isSuccess())
        {
            throw new Exception("db query fail");
        }

        $row = $rs->fetchRow();

        return $row["max_id"];
    }

	public function getMaxId()
	{
		return $this->getMax("id");
	}

    public function create()
    {
        $this->setSQLType("insert");
        $rs = $this->execute();

		if ($rs->isSuccess())
		{

			$this->setId($rs->insertId());
			return true;
		}
		else
		{
			return false;
		}

    }

    public function update()
    {
        $this->setSQLType("update");
        $rs = $this->execute();

        return $rs->isSuccess();
    }

    protected function formatCreateField()
    {
        $props = $this->getFields(); 
        $fields = NULL;
        $values = NULL;
        if ($props)
        {
            $keys = array_keys($props);
            $fields = implode("`, `", $keys);
            $fields = "`".$fields."`";
            $values = array_values($props);
            $values = implode("', '", $values); 
            $values = "'".$values."'";
        }

        return  array($fields, $values);
    }

    protected function formatUpdateField()
    {
        $fields = $this->getFields();
        $field = NULL;
        if ($fields)
        {
            foreach ($fields as $k => $v)
            {
                $field .= "`".$k."` = "."'".$v."', ";
            }
            $field = substr($field, 0, count($field) - 3);
        }

        return  $field;
    }

    protected function getFields()
    {
        return $this->fields;
    }

    protected function formatSelectField()
    {
        $fields = $this->getFields();
        $field = NULL;
        if ($fields)
        {
            foreach ($fields as $k => $v)
            {
                $field .= "`".$k."` = ".'"'.$v.'" AND ';
            }
            $field = substr($field, 0, count($field) - 6);
        }

        return  $field;
    }

    public function delete()
    {
        $this->setSQLType("delete");
        $rs = $this->execute();

        return $rs->isSuccess();
    }

    public function appendWhere($str)
    {
        $this->where[] = $str;
    }

    public function setWhere($str)
    {
        $this->where[] = $str;
    }

    protected function buildWhereClause()
    {
        $str = implode(" AND ", $this->getWhere());

        $sql_type = &$this->getSQLType();
        if ("select" == $sql_type || "delete" == $sql_type)
        {
            $field = $this->formatSelectField();

            if ($field)
            {
                $str = ($str) ? $field." AND ".$str : $field;
            }
        }

        return $str;
    }

    protected function checkWhereClause($where)
    {
        if (!$where)
        {
            throw new Exception("no where clause");
        }
    }

    public function query($sql)
    {
        $rs = $this->getDB()->query($sql);
        if (method_exists($this, "formatRow"))
        {
            $rs->addRowFormatter(array($this, 'formatRow'));
        }

        return $rs;
    }

    public function getDB()
    {
        if (!$this->db)
        {
			$dsn = $this->getDsn();
			if (!$dsn)
			{
				throw new Exception("no dsn");
			}

            $this->db = &NICED_DBFactory::create($dsn);
        }

        return $this->db;
    }

    public function getDsn()
    {
        return $this->dsn;
    }


    public function setDsn($dsn)
    {
        $this->dsn = $dsn;
    }

	public function setTablename($tablename)
	{
		$this->tablename = $tablename;
	}

    protected function handle($e)
    {
        $this->handler->handle($e);
    }

    public function __call($method, $args)
    {
        $prefix = substr($method, 0, 3);        
        $prop   = strtolower(substr($method, 3));
        $fields = &$this->fields;
        $props  = $this->props;
        $class_attr = &$this->classAttr;

        if ($prefix == "get")
        {
            if (in_array($prop, $props))
            {
                return $this->$prop;
            }
            else if (isset($fields[$prop]))
            {
                return $fields[$prop];
            }
            else if (isset($class_attr[$prop]))
            {
                return $class_attr[$prop];
            }
            else
            {
                return NULL;
            }
        }
        else if ($prefix == "set")
        {
            if (in_array($prop, $props))
            {
                $this->$prop = $args[0];
            }
            else if ($this->validFields)
            {
                if (in_array($prop, $this->validFields))
                {
                    $fields[$prop] = $args[0];
                }
                else
                {
                    $class_attr[$prop] = $args[0];
                }
            }
            else
            {
                $fields[$prop] = $args[0];
            }
        }
		else
		{
			throw new Exception("not support magic method prefix ".$prefix." for method ".$method);
		}
    }

	public function setField($k, $v)
	{
		$fields = &$this->fields;
		$fields[$k] = $v;
	}

	public function getField($k)
	{
		$fields = $this->getFields();
		
		return isset($fields[$k]) ? $fields[$k] : NULL;
	}

    public function __destruct()
    {
        unset($this->db);
    }
}//end class


/**
 *
 * @usage
 *   $user = ORMFactory::get("user", 1); 
 *   $user->setId(1);
 *   $user->setName("nana");
 *   $user->setEmail("qixiaona@gmail.com");
 *   $user->setPassword("abc");
 *   $user->load();
 *
 *   $orm      = ORMFactory::get("acl.acl_access");
 *
 *
 *
 *
 *
 */
class ORMFactory
{
	private static $orms = array(
		"acl.acl_access" => array("dsn" => "default", "tablename" => "access"),	
	);

    public static function get($name, $id = NULL)
    {
        $name = strtolower($name);
		//for directory seperator
        $slice = explode(".", $name);
        $count = count($slice);
        $name  = $slice[$count - 1];
		//for class seperator
        $name_slice = explode("_", $name);

        if (count($name_slice) > 1)
        {
            $file_name   = strtolower($name_slice[1]);
            $class_name  = ucfirst($name_slice[0]).ucfirst($name_slice[1]);
        }
        else
        {
            $file_name = strtolower($name);
            $class_name = ucfirst($name); 
        }


        if ($count > 1)
        {
            array_pop($slice);

            foreach ($slice as $k => $v)
            {
                $slice[$k] = strtolower($v);
            }
        }

        $base = implode(DS, $slice);
        $filename = DIR_LIBRARY."orm".DS.$base.DS.strtolower($file_name).".php";

		if (file_exists($filename))
		{
			require_once($filename);
			if (class_exists($class_name))
			{
				$orm = new $class_name($id);        
			}
			else
			{
				throw new Exception("no class found for class name ".$class_name);
			}
		}
		else
		{
			$class_name = "ORM";	
			$orm = new $class_name($id);        
			$orms = self::orms;
			$conf = $orms[$name];
			$orm->setDsn($conf["dsn"]);
			$orm->setTablename($conf["tablename"]);
		}


        return $orm;
    }
}//end class
